package strain.domain.interval_set_alt;

import static strain.domain.CompareWrapper.less;
import static strain.domain.CompareWrapper.lessOrEqual;

public class IntervalBound<T extends Comparable<T>> implements Comparable<IntervalBound<T>> {
    
    private final T       bound;
    private final boolean isLower;
    private final boolean isInclusive;
    
    public IntervalBound(final T bound, final boolean isLowerBound, final boolean isInclusive){
        if (bound == null){
            throw new IllegalArgumentException("bounding value must not be null");
        }
        this.bound = bound;
        this.isLower = isLowerBound;
        this.isInclusive = isInclusive;
    }
    
    public IntervalBound(final T bound, final boolean isLowerBound){
        this(bound, isLowerBound, true);
    }

    public boolean isLower(){
        return this.isLower;
    }

    public boolean isUpper(){
        return !this.isLower;
    }
    
    public boolean isInclusive(){
        return this.isInclusive;
    }

    public boolean isExclusive(){
        return !this.isInclusive;
    }
    
    public T getBound(){
        return this.bound;
    }

    public boolean contains(final T t){
        if (this.isLower){
            if (this.isInclusive){
                return lessOrEqual(this.bound, t);
            }
            // if ((this.isLower) && (!this.isInclusive))
            return less(this.bound, t);
        }
        // if ((!this.isLower) && (!this.isLower))
        if (this.isInclusive){
            return lessOrEqual(t, this.bound);
        }
        // if ((!this.isLower) && (!this.isInclusive))
        return less(t, this.bound);
    }

    public IntervalBound<T> complement(){
        return new IntervalBound<T>(this.bound, !this.isLower, !this.isInclusive);
    }
    
    // combine pairwise {[a b], [a b), [b c], (b c]}
    // [a b], [a b) :  U=[a b]...  N=[a b)
    // [a b], [b c] :  U=[a c]...  N=[b b]
    // [a b], (b c] :  U=[a c]...  N={}
    
    // [a b), [b c] :  U=[a c]...  N={}
    // [a b), (b c] :  U=[a b) (b c]...  N={}
    
    // [b c], (b c] :  U=[b c]...  N=(b c]


    public int compareTo(final IntervalBound<T> ib) {
        // if the bounding value is different, then it defines the order
        int comp = this.bound.compareTo(ib.bound);
        if (comp != 0) return comp;
        
        // bounding value is equal, now we have to sort out  x) [x x] (x
        // x) == x), [x == [x, x] == x], (x == (x
        if ((this.isLower == ib.isLower) && (this.isInclusive == ib.isInclusive)) return 0;
        if (!this.isInclusive){
            if (!this.isLower) return -1;   // x) is lowest remaining
            return 1;                       // (x is highest remaining
        }
        if (!ib.isInclusive){
            if (!ib.isLower) return 1;  // x) is lowest remaining
            return -1;                  // (x is highest remaining
        }
        // only possibility left isInclusive for both
        if (this.isLower) return -1;
        return 1;
    }
    
    @Override
    public String toString(){
        StringBuilder sb = new StringBuilder();
        if (this.isLower){
            if (this.isInclusive) sb.append("[");
            else sb.append("(");
            sb.append(this.bound);
        } else {
            sb.append(this.bound);
            if (this.isInclusive) sb.append("]");
            else sb.append(")");
        }
        return sb.toString();
    }
    
    @Override
    public int hashCode(){
        int result = this.bound.hashCode();
        result += result * 31 + new Boolean(this.isLower).hashCode();
        result += result * 31 + new Boolean(this.isInclusive).hashCode();
        return result;
    }
    
    @SuppressWarnings("unchecked")
    @Override
    public boolean equals(final Object o){
        if (this == o) return true;
        if ((o == null) || (!getClass().equals(o.getClass()))) return false;
        IntervalBound<T> ib = (IntervalBound<T>) o;
        boolean result = (this.bound == null) ? (ib.bound == null) : (this.bound.equals(ib.bound));
        result = result && (this.isLower == ib.isLower) && (this.isInclusive == ib.isInclusive);
        return result;
    }
    
}
